summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCharles Lombardo <clombardo169@gmail.com>2023-09-26 19:26:06 +0200
committerCharles Lombardo <clombardo169@gmail.com>2023-09-26 19:26:20 +0200
commitc8673a16bbf84bcbacbe73cfae5500bc3bfe992b (patch)
tree0c350a7f2501e693e3eb1906679ae0dc7957893b
parentandroid: Consolidate installers to one fragment (diff)
downloadyuzu-c8673a16bbf84bcbacbe73cfae5500bc3bfe992b.tar
yuzu-c8673a16bbf84bcbacbe73cfae5500bc3bfe992b.tar.gz
yuzu-c8673a16bbf84bcbacbe73cfae5500bc3bfe992b.tar.bz2
yuzu-c8673a16bbf84bcbacbe73cfae5500bc3bfe992b.tar.lz
yuzu-c8673a16bbf84bcbacbe73cfae5500bc3bfe992b.tar.xz
yuzu-c8673a16bbf84bcbacbe73cfae5500bc3bfe992b.tar.zst
yuzu-c8673a16bbf84bcbacbe73cfae5500bc3bfe992b.zip
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt6
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt106
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt67
-rw-r--r--src/android/app/src/main/res/values/strings.xml1
4 files changed, 89 insertions, 91 deletions
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt
index d6418a666..16a794dee 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt
@@ -50,3 +50,9 @@ class TaskViewModel : ViewModel() {
}
}
}
+
+enum class TaskState {
+ Completed,
+ Failed,
+ Cancelled
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
index 1164dfe94..0cb701476 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
@@ -51,17 +51,16 @@ import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
import org.yuzu.yuzu_emu.getPublicFilesDir
import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.model.HomeViewModel
+import org.yuzu.yuzu_emu.model.TaskState
import org.yuzu.yuzu_emu.model.TaskViewModel
import org.yuzu.yuzu_emu.utils.*
import java.io.BufferedInputStream
import java.io.BufferedOutputStream
-import java.io.FileInputStream
import java.io.FileOutputStream
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
-import java.util.zip.ZipOutputStream
class MainActivity : AppCompatActivity(), ThemeProvider {
private lateinit var binding: ActivityMainBinding
@@ -396,7 +395,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
val task: () -> Any = {
var messageToShow: Any
try {
- FileUtil.unzip(inputZip, cacheFirmwareDir)
+ FileUtil.unzipToInternalStorage(BufferedInputStream(inputZip), cacheFirmwareDir)
val unfilteredNumOfFiles = cacheFirmwareDir.list()?.size ?: -1
val filteredNumOfFiles = cacheFirmwareDir.list(filterNCA)?.size ?: -2
messageToShow = if (unfilteredNumOfFiles != filteredNumOfFiles) {
@@ -639,35 +638,17 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
R.string.exporting_user_data,
true
) {
- val zos = ZipOutputStream(
- BufferedOutputStream(contentResolver.openOutputStream(result))
+ val zipResult = FileUtil.zipFromInternalStorage(
+ File(DirectoryInitialization.userDirectory!!),
+ DirectoryInitialization.userDirectory!!,
+ BufferedOutputStream(contentResolver.openOutputStream(result)),
+ taskViewModel.cancelled
)
- zos.use { stream ->
- File(DirectoryInitialization.userDirectory!!).walkTopDown().forEach { file ->
- if (taskViewModel.cancelled.value) {
- return@newInstance R.string.user_data_export_cancelled
- }
-
- if (!file.isDirectory) {
- val newPath = file.path.substring(
- DirectoryInitialization.userDirectory!!.length,
- file.path.length
- )
- stream.putNextEntry(ZipEntry(newPath))
-
- val buffer = ByteArray(8096)
- var read: Int
- FileInputStream(file).use { fis ->
- while (fis.read(buffer).also { read = it } != -1) {
- stream.write(buffer, 0, read)
- }
- }
-
- stream.closeEntry()
- }
- }
+ return@newInstance when (zipResult) {
+ TaskState.Completed -> getString(R.string.user_data_export_success)
+ TaskState.Failed -> R.string.export_failed
+ TaskState.Cancelled -> R.string.user_data_export_cancelled
}
- return@newInstance getString(R.string.user_data_export_success)
}.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
}
@@ -698,40 +679,17 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
return@newInstance getString(R.string.invalid_yuzu_backup)
}
+ // Clear existing user data
File(DirectoryInitialization.userDirectory!!).deleteRecursively()
- val zis =
- ZipInputStream(BufferedInputStream(contentResolver.openInputStream(result)))
- val userDirectory = File(DirectoryInitialization.userDirectory!!)
- val canonicalPath = userDirectory.canonicalPath + '/'
- zis.use { stream ->
- var ze: ZipEntry? = stream.nextEntry
- while (ze != null) {
- val newFile = File(userDirectory, ze!!.name)
- val destinationDirectory =
- if (ze!!.isDirectory) newFile else newFile.parentFile
-
- if (!newFile.canonicalPath.startsWith(canonicalPath)) {
- throw SecurityException(
- "Zip file attempted path traversal! ${ze!!.name}"
- )
- }
-
- if (!destinationDirectory.isDirectory && !destinationDirectory.mkdirs()) {
- throw IOException("Failed to create directory $destinationDirectory")
- }
-
- if (!ze!!.isDirectory) {
- val buffer = ByteArray(8096)
- var read: Int
- BufferedOutputStream(FileOutputStream(newFile)).use { bos ->
- while (zis.read(buffer).also { read = it } != -1) {
- bos.write(buffer, 0, read)
- }
- }
- }
- ze = stream.nextEntry
- }
+ // Copy archive to internal storage
+ try {
+ FileUtil.unzipToInternalStorage(
+ BufferedInputStream(contentResolver.openInputStream(result)),
+ File(DirectoryInitialization.userDirectory!!)
+ )
+ } catch (e: Exception) {
+ return@newInstance getString(R.string.invalid_yuzu_backup)
}
// Reinitialize relevant data
@@ -758,19 +716,13 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
}.zip"
)
outputZipFile.createNewFile()
- ZipOutputStream(BufferedOutputStream(FileOutputStream(outputZipFile))).use { zos ->
- saveFolder.walkTopDown().forEach { file ->
- val zipFileName =
- file.absolutePath.removePrefix(savesFolderRoot).removePrefix("/")
- if (zipFileName == "") {
- return@forEach
- }
- val entry = ZipEntry("$zipFileName${(if (file.isDirectory) "/" else "")}")
- zos.putNextEntry(entry)
- if (file.isFile) {
- file.inputStream().use { fis -> fis.copyTo(zos) }
- }
- }
+ val result = FileUtil.zipFromInternalStorage(
+ saveFolder,
+ savesFolderRoot,
+ BufferedOutputStream(FileOutputStream(outputZipFile))
+ )
+ if (result == TaskState.Failed) {
+ return false
}
lastZipCreated = outputZipFile
} catch (e: Exception) {
@@ -832,7 +784,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
NativeLibrary.initializeEmptyUserDirectory()
- val inputZip = applicationContext.contentResolver.openInputStream(result)
+ val inputZip = contentResolver.openInputStream(result)
// A zip needs to have at least one subfolder named after a TitleId in order to be considered valid.
var validZip = false
val savesFolder = File(savesFolderRoot)
@@ -853,7 +805,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
try {
CoroutineScope(Dispatchers.IO).launch {
- FileUtil.unzip(inputZip, cacheSaveDir)
+ FileUtil.unzipToInternalStorage(BufferedInputStream(inputZip), cacheSaveDir)
cacheSaveDir.list(filterTitleId)?.forEach { savePath ->
File(savesFolder, savePath).deleteRecursively()
File(cacheSaveDir, savePath).copyRecursively(
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt
index 142af5f26..c3f53f1c5 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt
@@ -8,6 +8,7 @@ import android.database.Cursor
import android.net.Uri
import android.provider.DocumentsContract
import androidx.documentfile.provider.DocumentFile
+import kotlinx.coroutines.flow.StateFlow
import java.io.BufferedInputStream
import java.io.File
import java.io.FileOutputStream
@@ -18,6 +19,9 @@ import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.model.MinimalDocumentFile
+import org.yuzu.yuzu_emu.model.TaskState
+import java.io.BufferedOutputStream
+import java.util.zip.ZipOutputStream
object FileUtil {
const val PATH_TREE = "tree"
@@ -282,30 +286,65 @@ object FileUtil {
/**
* Extracts the given zip file into the given directory.
- * @exception IOException if the file was being created outside of the target directory
*/
@Throws(SecurityException::class)
- fun unzip(zipStream: InputStream, destDir: File): Boolean {
- ZipInputStream(BufferedInputStream(zipStream)).use { zis ->
+ fun unzipToInternalStorage(zipStream: BufferedInputStream, destDir: File) {
+ ZipInputStream(zipStream).use { zis ->
var entry: ZipEntry? = zis.nextEntry
while (entry != null) {
- val entryName = entry.name
- val entryFile = File(destDir, entryName)
- if (!entryFile.canonicalPath.startsWith(destDir.canonicalPath + File.separator)) {
- throw SecurityException("Entry is outside of the target dir: " + entryFile.name)
+ val newFile = File(destDir, entry.name)
+ val destinationDirectory = if (entry.isDirectory) newFile else newFile.parentFile
+
+ if (!newFile.canonicalPath.startsWith(destDir.canonicalPath + File.separator)) {
+ throw SecurityException("Zip file attempted path traversal! ${entry.name}")
+ }
+
+ if (!destinationDirectory.isDirectory && !destinationDirectory.mkdirs()) {
+ throw IOException("Failed to create directory $destinationDirectory")
}
- if (entry.isDirectory) {
- entryFile.mkdirs()
- } else {
- entryFile.parentFile?.mkdirs()
- entryFile.createNewFile()
- entryFile.outputStream().use { fos -> zis.copyTo(fos) }
+
+ if (!entry.isDirectory) {
+ newFile.outputStream().use { fos -> zis.copyTo(fos) }
}
entry = zis.nextEntry
}
}
+ }
- return true
+ /**
+ * Creates a zip file from a directory within internal storage
+ * @param inputFile File representation of the item that will be zipped
+ * @param rootDir Directory containing the inputFile
+ * @param outputStream Stream where the zip file will be output
+ */
+ fun zipFromInternalStorage(
+ inputFile: File,
+ rootDir: String,
+ outputStream: BufferedOutputStream,
+ cancelled: StateFlow<Boolean>? = null
+ ): TaskState {
+ try {
+ ZipOutputStream(outputStream).use { zos ->
+ inputFile.walkTopDown().forEach { file ->
+ if (cancelled?.value == true) {
+ return TaskState.Cancelled
+ }
+
+ if (!file.isDirectory) {
+ val entryName =
+ file.absolutePath.removePrefix(rootDir).removePrefix("/")
+ val entry = ZipEntry(entryName)
+ zos.putNextEntry(entry)
+ if (file.isFile) {
+ file.inputStream().use { fis -> fis.copyTo(zos) }
+ }
+ }
+ }
+ }
+ } catch (e: Exception) {
+ return TaskState.Failed
+ }
+ return TaskState.Completed
}
fun isRootTreeUri(uri: Uri): Boolean {
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index 067141866..485d4c1dd 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -229,6 +229,7 @@
<string name="string_null">Null</string>
<string name="string_import">Import</string>
<string name="export">Export</string>
+ <string name="export_failed">Export failed</string>
<string name="cancelling">Cancelling</string>
<!-- GPU driver installation -->